import type { RequestParams } from "@/api";
import type {
	BaseSearchModel,
	ListModel,
	ListSearchModel,
	PagedListSearchModel,
} from "@/types";
import { isArray, isFunction } from "@/utils";
import {
	type PaginationProps,
	Table
} from "@arco-design/web-react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import type React from "react";
import {
	type RefAttributes,
	useCallback,
	useImperativeHandle,
	useRef,
	useState,
} from "react";
import { useListViewContext } from "../ListView/context";
import { StContext } from "./context";
import { renderColumn } from "./render-column";
import type { StProps, StRef } from "./types";

export * from './types';

const defaultPagination: PaginationProps = {
	total: 0,
	current: 1,
	showTotal: true,
	defaultPageSize: 20,
	sizeOptions: [10, 20, 30, 40, 50, 100, 200, 300, 500, 1000],
};

export default function St<
	TModel = unknown,
	TSearch extends BaseSearchModel | undefined = undefined,
>(
	props: StProps<TModel, TSearch> &
		RefAttributes<StRef<TModel>>,
): React.ReactNode {
	const staticRef = useRef<StRef<TModel>>(null);
	const dynamicRef = useRef<StRef<TModel>>(null);

	useImperativeHandle(
		props.ref,
		() => {
			return {
				reload() {
					if (isFunction(props.data)) {
						dynamicRef.current?.reload();
					} else {
						staticRef.current?.reload();
					}
				},
				load(pageIndex: number) {
					if (isFunction(props.data)) {
						dynamicRef.current?.load(pageIndex);
					} else {
						staticRef.current?.load(pageIndex);
					}
				},
				getSelectedRows() {
					if (isFunction(props.data)) {
						return dynamicRef.current?.getSelectedRows() ?? [];
					}
					return staticRef.current?.getSelectedRows() ?? [];
				},
				data: isFunction(props.data)
					? (dynamicRef.current?.data ?? [])
					: (staticRef.current?.data ?? []),
				rowKey: isFunction(props.data)
					? (dynamicRef.current?.rowKey ?? "")
					: (staticRef.current?.rowKey ?? ""),
			};
		},
		[props.data],
	);

	if (isFunction(props.data)) {
		return <DynamicSt<TModel, TSearch> {...props} ref={dynamicRef} />;
	}
	return <StaticSt<TModel, TSearch> {...props} ref={staticRef} />;
}

function StaticSt<
	TModel,
	TSearch extends BaseSearchModel | undefined = undefined,
>({
	data,
	columns,
	rowKey = "id",
	hidePagination = false,
	pageSize = 20,
	ref,
	rowSelection,
	selectedKeys,
	onSelectedKeysChange,
}: StProps<TModel, TSearch> &
	RefAttributes<StRef<TModel>>): React.ReactNode {
	const [pagination, setPagination] = useState<PaginationProps>({
		...defaultPagination, pageSize,
	});

	if (isFunction(data)) return null;

	const dataSource = isArray(data) ? data : (data as ListModel<TModel>).list || [];
	const total = isArray(data) ? data.length : (data as ListModel<TModel>).total;

	const handleTableChange = (newPagination: PaginationProps) => {
		setPagination((p) => ({ ...p, ...newPagination }));
	};

	const tableColumns = columns.map((c) => renderColumn(c));

	const handleRowSelectionChange = (selectedRowKeys: (string | number)[]) => {
		if (rowSelection) {
			onSelectedKeysChange?.(selectedRowKeys);
		}
	};

	const getSelectedRows = useCallback(() => {
		const selectedRows = new Set<TModel>();
		for (const key of selectedKeys ?? []) {
			const row = dataSource.find((item) => item[rowKey as string] === key);
			if (!row) continue;
			selectedRows.add(row);
		}
		return Array.from(selectedRows);
	}, [dataSource, selectedKeys, rowKey]);

	useImperativeHandle(
		ref,
		() => ({
			reload() { },
			load(pageIndex: number) {
				setPagination((p) => ({
					...p, current: pageIndex,
				}));
			},
			getSelectedRows,
			data: dataSource,
			rowKey: rowKey as string,
		}),
		[getSelectedRows, dataSource, rowKey],
	);

	return (
		<Table<TModel>
			columns={tableColumns}
			data={dataSource}
			rowKey={rowKey}
			pagination={hidePagination ? false : { ...pagination, total }}
			border
			onChange={handleTableChange}
			rowSelection={
				rowSelection
					? {
						type: rowSelection,
						selectedRowKeys: selectedKeys,
						onChange: handleRowSelectionChange,
					}
					: undefined
			}
		/>
	);
}

function DynamicSt<
	TModel = unknown,
	TSearch extends BaseSearchModel | undefined = BaseSearchModel,
>({
	data,
	columns,
	searchModel,
	rowKey = "id",
	hidePagination = false,
	pageSize = 20,
	queryKey = "",
	ref,
	rowSelection,
	selectedKeys,
	onSelectedKeysChange,
}: StProps<TModel, TSearch> &
	RefAttributes<StRef<TModel>>): React.ReactNode {
	const [pagination, setPagination] = useState<PaginationProps>({
		...defaultPagination, pageSize,
	});

	type RequestFunction = (
		req: ListSearchModel<TSearch> | PagedListSearchModel<TSearch>,
		params?: RequestParams,
	) => Promise<TModel[] | Partial<ListModel<TModel>>>;

	const pathRegex = /path: `([\w/\-]+)`/g;
	const requestFunctionString = (data as RequestFunction).toString();
	const queryName =
		queryKey ??
		pathRegex.exec(requestFunctionString)?.[1] ??
		requestFunctionString;

	const lvContext = useListViewContext();
	const tableSearchModel = lvContext
		? (lvContext.searchModel as TSearch)
		: searchModel;

	const {
		data: queryResult,
		isFetching,
		refetch,
	} = useQuery({
		queryKey: [
			queryName,
			tableSearchModel,
			pagination.current,
			pagination.pageSize,
		],
		queryFn: () =>
			(data as RequestFunction)({
				...(tableSearchModel ?? ({} as TSearch)),
				pageIndex: pagination.current,
				pageSize: pagination.pageSize,
			}),
		refetchOnWindowFocus: false,
		placeholderData: keepPreviousData,
	});

	const handleTableChange = (newPagination: PaginationProps) => {
		setPagination((p) => ({ ...p, ...newPagination }));
	};

	const dataSource = isArray(queryResult)
		? queryResult
		: queryResult?.list || [];
	const total = isArray(queryResult) ? queryResult.length : queryResult?.total;

	const tableColumns = columns.map((c) => renderColumn(c));

	const reloadData = useCallback(() => {
		refetch();
	}, [refetch]);

	const loadData = useCallback((pageIndex: number) => {
		setPagination((p) => ({
			...p, current: pageIndex,
		}));
	}, []);

	const handleRowSelectionChange = (selectedRowKeys: (string | number)[]) => {
		if (rowSelection) {
			onSelectedKeysChange?.(selectedRowKeys);
		}
	};

	const getSelectedRows = useCallback(() => {
		const selectedRows = new Set<TModel>();
		for (const key of selectedKeys ?? []) {
			const row = dataSource.find((item) => item[rowKey as string] === key);
			if (!row) continue;
			selectedRows.add(row);
		}
		return Array.from(selectedRows);
	}, [dataSource, selectedKeys, rowKey]);

	useImperativeHandle(
		ref,
		() => ({
			reload: reloadData,
			load: loadData,
			getSelectedRows,
			data: dataSource,
			rowKey: rowKey as string,
		}),
		[reloadData, loadData, getSelectedRows, dataSource, rowKey],
	);

	return (
		<StContext.Provider value={{ reloadData: reloadData, loadData }}>
			<Table<TModel>
				columns={tableColumns}
				data={dataSource}
				rowKey={rowKey}
				pagination={
					hidePagination ? false : { ...pagination, total }
				}
				border
				loading={isFetching}
				onChange={handleTableChange}
				rowSelection={
					rowSelection
						? {
							type: rowSelection,
							selectedRowKeys: selectedKeys,
							onChange: handleRowSelectionChange,
						}
						: undefined
				}
			/>
		</StContext.Provider>
	);
}
